{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 导入数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "# 读取数据\n",
    "data = np.loadtxt('data/Abalone.csv', delimiter = \",\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. 数据预处理"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "40%做测试集，60%做训练集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "trainX, testX, trainY, testY = train_test_split(data[:,0:-1], data[:,-1], test_size = 0.4, random_state = 32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. 参数初始化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里，我们要初始化参数$W$和$b$，其中$W \\in \\mathbb{R}^m$，$b \\in \\mathbb{R}$，初始化的策略是将$W$初始化成一个随机数矩阵，参数$b$为0。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "def initialize(m):\n",
    "    '''\n",
    "    参数初始化，将W初始化成一个随机向量，b是一个长度为1的向量\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    m: int, 特征数\n",
    "    \n",
    "    Returns\n",
    "    ----------\n",
    "    W: np.ndarray, shape = (m, ), 参数W\n",
    "    \n",
    "    b: np.ndarray, shape = (1, ), 参数b\n",
    "    \n",
    "    '''\n",
    "    \n",
    "    # 指定随机种子，这样生成的随机数就是固定的了，这样就可以与下面的测试样例进行比对\n",
    "    np.random.seed(32)\n",
    "    \n",
    "    W = np.random.normal(size = (m, )) * 0.01\n",
    "    \n",
    "    b = np.zeros((1, ))\n",
    "    \n",
    "    return W, b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. 前向传播"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里，我们要完成输入矩阵$X$在神经网络中的计算，也就是完成 $Z = XW + b$ 的计算。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "def forward(X, W, b):\n",
    "    '''\n",
    "    前向传播，计算Z = XW + b\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    X: np.ndarray, shape = (n, m)，输入的数据\n",
    "    \n",
    "    W: np.ndarray, shape = (m, )，权重\n",
    "    \n",
    "    b: np.ndarray, shape = (1, )，偏置\n",
    "    \n",
    "    Returns\n",
    "    ----------\n",
    "    Z: np.ndarray, shape = (n, )，线性组合后的值\n",
    "    \n",
    "    '''\n",
    "    \n",
    "    # 完成Z = XW + b的计算\n",
    "    # YOUR CODE HERE\n",
    "    Z = np.dot(X,W)+ b\n",
    "    \n",
    "    return Z"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. 损失函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mse(y_true, y_pred):\n",
    "    '''\n",
    "    MSE，均方误差\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    y_true: np.ndarray, shape = (n, )，真值\n",
    "    \n",
    "    y_pred: np.ndarray, shape = (n, )，预测值\n",
    "    \n",
    "    Returns\n",
    "    ----------\n",
    "    loss: float，损失值\n",
    "    \n",
    "    '''\n",
    "    \n",
    "    # 计算MSE\n",
    "    # YOUR CODE HERE\n",
    "    n = y_pred.shape[0]\n",
    "    loss =sum((y_pred - y_true) * (y_pred - y_true)) / n\n",
    "    \n",
    "    return loss"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. 反向传播"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_gradient(X, Z, y_true):\n",
    "    '''\n",
    "    计算梯度\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    X: np.ndarray, shape = (n, m)，输入的数据\n",
    "    \n",
    "    Z: np.ndarray, shape = (n, )，线性组合后的值\n",
    "    \n",
    "    y_true: np.ndarray, shape = (n, )，真值\n",
    "    \n",
    "    Returns\n",
    "    ----------\n",
    "    dW, np.ndarray, shape = (m, ), 参数W的梯度\n",
    "    \n",
    "    db, np.ndarray, shape = (1, ), 参数b的梯度\n",
    "    \n",
    "    '''\n",
    "    \n",
    "    n = len(y_true)\n",
    "    \n",
    "    # 计算W的梯度\n",
    "    # YOUR CODE HERE\n",
    "    dW = np.dot(X.T,(Z - y_true)) * 2 / n\n",
    "    \n",
    "    # 计算b的梯度\n",
    "    # YOUR CODE HERE\n",
    "    db = sum(Z - y_true) * 2 / n \n",
    "    return dW, db"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. 梯度下降"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "def update(dW, db, W, b, learning_rate):\n",
    "    '''\n",
    "    梯度下降，参数更新，不需要返回值，W和b实际上是以引用的形式传入到函数内部，\n",
    "    函数内改变W和b会直接影响到它们本身，所以不需要返回值\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    dW, np.ndarray, shape = (m, ), 参数W的梯度\n",
    "    \n",
    "    db, np.ndarray, shape = (1, ), 参数b的梯度\n",
    "    \n",
    "    W: np.ndarray, shape = (m, )，权重\n",
    "    \n",
    "    b: np.ndarray, shape = (1, )，偏置\n",
    "    \n",
    "    learning_rate, float，学习率\n",
    "    \n",
    "    '''\n",
    "    # 更新W\n",
    "    W -= learning_rate * dW\n",
    "    \n",
    "    # 更新b\n",
    "    b -= learning_rate * db"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "完成整个参数更新的过程，先计算梯度，再更新参数，将compute_gradient和update组装在一起。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "def backward(X, Z, y_true, W, b, learning_rate):\n",
    "    '''\n",
    "    使用compute_gradient和update函数，先计算梯度，再更新参数\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    X: np.ndarray, shape = (n, m)，输入的数据\n",
    "    \n",
    "    Z: np.ndarray, shape = (n, )，线性组合后的值\n",
    "    \n",
    "    y_true: np.ndarray, shape = (n, )，真值\n",
    "    \n",
    "    W: np.ndarray, shape = (m, )，权重\n",
    "    \n",
    "    b: np.ndarray, shape = (1, )，偏置\n",
    "    \n",
    "    learning_rate, float，学习率\n",
    "    \n",
    "    '''\n",
    "    # 计算参数的梯度\n",
    "    # YOUR CODE HERE\n",
    "    dw,db = compute_gradient(X, Z, y_true)\n",
    "    # 更新参数\n",
    "    # YOUR CODE HERE\n",
    "    update(dw, db, W, b, learning_rate)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(trainX, trainY, testX, testY, W, b, epochs, learning_rate = 0.01, verbose = False):\n",
    "    '''\n",
    "    训练，我们要迭代epochs次，每次迭代的过程中，做一次前向传播和一次反向传播，更新参数\n",
    "    同时记录训练集和测试集上的损失值，后面画图用。然后循环往复，直到达到最大迭代次数epochs\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    trainX: np.ndarray, shape = (n, m), 训练集\n",
    "    \n",
    "    trainY: np.ndarray, shape = (n, ), 训练集标记\n",
    "    \n",
    "    testX: np.ndarray, shape = (n_test, m)，测试集\n",
    "    \n",
    "    testY: np.ndarray, shape = (n_test, )，测试集的标记\n",
    "    \n",
    "    W: np.ndarray, shape = (m, )，参数W\n",
    "    \n",
    "    b: np.ndarray, shape = (1, )，参数b\n",
    "    \n",
    "    epochs: int, 要迭代的轮数\n",
    "    \n",
    "    learning_rate: float, default 0.01，学习率\n",
    "    \n",
    "    verbose: boolean, default False，是否打印损失值\n",
    "    \n",
    "    Returns\n",
    "    ----------\n",
    "    training_loss_list: list(float)，每迭代一次之后，训练集上的损失值\n",
    "    \n",
    "    testing_loss_list: list(float)，每迭代一次之后，测试集上的损失值\n",
    "    \n",
    "    '''\n",
    "    training_loss_list = []\n",
    "    testing_loss_list = []\n",
    "    \n",
    "    for epoch in range(epochs):\n",
    "        \n",
    "        # 这里我们要将神经网络的输出值保存起来，因为后面反向传播的时候需要这个值\n",
    "        Z = forward(trainX, W, b)\n",
    "        \n",
    "        # 计算训练集的损失值\n",
    "        training_loss = mse(trainY, Z)\n",
    "        \n",
    "        # 计算测试集的损失值        \n",
    "        testing_loss = mse(testY, forward(testX, W, b))\n",
    "        \n",
    "        # 将损失值存起来\n",
    "        training_loss_list.append(training_loss)\n",
    "        testing_loss_list.append(testing_loss)\n",
    "        \n",
    "        # 打印损失值，debug用\n",
    "        if verbose:\n",
    "            print('epoch %s training loss: %s'%(epoch+1, training_loss))\n",
    "            print('epoch %s testing loss: %s'%(epoch+1, testing_loss))\n",
    "            print()\n",
    "        \n",
    "        # 反向传播，参数更新\n",
    "        backward(trainX, Z, trainY, W, b, learning_rate)\n",
    "        \n",
    "    return training_loss_list, testing_loss_list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 9. 检查\n",
    "\n",
    "编写一个绘制损失值变化曲线的函数\n",
    "\n",
    "一般我们通过绘制损失函数的变化曲线来判断模型的拟合状态。\n",
    "\n",
    "一般来说，随着迭代轮数的增加，训练集的loss在下降，而测试集的loss在上升，这说明我们正在不断地让模型在训练集上表现得越来越好，在测试集上表现得越来越糟糕，这就是过拟合的体现。  \n",
    "\n",
    "如果训练集loss和测试集loss共同下降，这就是我们想要的结果，说明模型正在很好的学习。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_loss_curve(training_loss_list, testing_loss_list):\n",
    "    '''\n",
    "    绘制损失值变化曲线\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    training_loss_list: list(float)，每迭代一次之后，训练集上的损失值\n",
    "    \n",
    "    testing_loss_list: list(float)，每迭代一次之后，测试集上的损失值\n",
    "    \n",
    "    '''\n",
    "    plt.figure(figsize = (10, 6))\n",
    "    plt.plot(training_loss_list, label = 'training loss')\n",
    "    plt.plot(testing_loss_list, label = 'testing loss')\n",
    "    plt.xlabel('epoch')\n",
    "    plt.ylabel('loss')\n",
    "    plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面这些函数就是完成整个神经网络需要的函数了\n",
    "\n",
    "|函数名|功能|\n",
    "|-|-|\n",
    "|initialize | 参数初始化|\n",
    "|forward | 给定数据，计算神经网络的输出值|\n",
    "|mse | 给定真值，计算神经网络的预测值与真值之间的差距|\n",
    "|backward | 计算参数的梯度，并实现参数的更新|\n",
    "|compute_gradient | 计算参数的梯度|\n",
    "|update | 参数的更新|\n",
    "|backward | 计算参数梯度，并且更新参数|\n",
    "|train | 训练神经网络|\n",
    "|plot_loss_curve | 绘制损失函数的变化曲线|\n",
    "\n",
    "我们使用参数初始化函数和训练函数，完成神经网络的训练。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 10. 标准化处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "stand = StandardScaler()\n",
    "trainX_normalized = stand.fit_transform(trainX)\n",
    "testX_normalized = stand.transform(testX)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "m = trainX.shape[1]\n",
    "W, b = initialize(m)\n",
    "training_loss_list, testing_loss_list = train(trainX_normalized, trainY, testX_normalized, testY, W, b, 40, learning_rate = 0.1, verbose = False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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NDQ07Hp+/vnnd8v3RS5fvpyZM2ce8n56goXsICaMrGR1GkXOm/olSeqQ7i5k99xzDyNHjuyOaH2WhewgqspL2ZCro3Ln6qyjSJLUL1x11VW8+OKLzJkzhyuvvBKAr3/967z5zW9m9uzZXHPNNQDs2LGDCy64gOOOO46ZM2dy++23893vfpdVq1Zx5plncuaZZwIwZcoUNmzYwPLlyznmmGP4xCc+wYwZMzjnnHN2fb/lE088wezZszn55JO58sorD3olrLGxkY9+9KPMmjWLuXPn8sADDwDw7LPPcsIJJzBnzhxmz57NsmXL9pmzuzkOWQfUV45leNMzWceQJKnz/uMqWPOn7t3nuFlw3rX7XXzttdfyzDPP7BoZ/95772XZsmU8/vjjpJR417vexR/+8AfWr1/PhAkTuPvuu4HCd1yOGDGCb37zmzzwwAOMHj36DftetmwZt956Kz/4wQ+4+OKLueOOO7jsssv46Ec/yo033sgpp5zCVVddddBf4V/+5V8A+NOf/sSSJUs455xzWLp0KTfccANXXHEFH/rQh2hubqatrY177rnnDTm7m1fIOqCpagJDUgM0dv8fgCRJA929997Lvffey9y5c5k3bx5Llixh2bJlzJo1i/vuu48vf/nLPPTQQ4wYMeKg+5o6dSpz5swB4Pjjj2f58uVs2bKF7du3c8oppwBw6aWXHnQ/Dz/8MB/+8IcBOProo5k8eTJLly7l5JNP5h//8R/52te+xiuvvMKQIUO6lLOzvELWESMmwiZIW14lxnX/H4IkST3mAFeyektKiauvvppPfepTb1i2cOFC7rnnHq6++mrOOeccvvKVrxxwXxUVu781p6SkhJ07d9KV7+Xe3zaXXnopJ554InfffTfveMc7+OEPf8hZZ53V6Zyd5RWyDigfdTgADetXZJxEkqS+r7q6mu3bt+96/o53vIObbrqJ+vp6AF577TXWrVvHqlWrqKqq4rLLLuOLX/wiixYt2uf2B1NTU0N1dTWPPvooALfddttBtznttNP46U9/CsDSpUtZsWIFRx11FC+99BLTpk3j85//PO9617t4+umn95uzO3mFrAOGjZkCwLa1LzN0VrZZJEnq60aNGsWpp57KzJkzOe+88/j617/O4sWLOfnkkwEYNmwYt9xyCy+88AJXXnkluVyOsrIyrr/+egA++clPct555zF+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\n",
      "text/plain": [
       "<Figure size 720x432 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_loss_curve(training_loss_list, testing_loss_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.69\n",
      "2.3347845419905204\n",
      "5.451218857517885\n"
     ]
    }
   ],
   "source": [
    "from sklearn.metrics import mean_absolute_error\n",
    "prediction = forward(testX_normalized, W, b)\n",
    "rmse = mse(testY, prediction) ** 0.5\n",
    "mae = round(mean_absolute_error(testY, prediction),2)\n",
    "mse = mse(testY, prediction)\n",
    "print(mae)\n",
    "print(rmse)\n",
    "print(mse)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###### 神经网络\n",
    "\n",
    "MAE|RMSE|MSE\n",
    "-|-|-\n",
    "1.69|2.33|5.45"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
